package types

import (
	"fmt"
	llq "github.com/emirpasic/gods/queues/linkedlistqueue"

	"mig/pkg/collections/bloom_filter"
	"mig/pkg/digester"
	"mig/pkg/utils"
)

type UnequalChunk struct {
	ChunkNo int
	Node    *DigestNode
}

type CompareResult struct {
	IsComparable  bool
	UnequalChunks []UnequalChunk
	DigestTree    *DigestTree
}

func (c *CompareResult) Print() {
	fmt.Println("IsComparable:", c.IsComparable)
	var unequalChunkNo []int
	for _, chunk := range c.UnequalChunks {
		unequalChunkNo = append(unequalChunkNo, chunk.ChunkNo)
	}
	if len(unequalChunkNo) > 0 {
		fmt.Println("Unequal Chunk No:", utils.FormatJSONString(unequalChunkNo))
	} else {
		fmt.Println("All Equal")
	}
}

type IntegrityInfo struct {
	DigestNodes []*DigestNode
	DigestTree  *DigestTree
	BloomFilter *bloom_filter.BloomFilter
}

func (r *IntegrityInfo) Empty() bool {
	if r == nil {
		return true
	}
	if r.DigestTree != nil {
		return r.DigestTree.Empty()
	}
	if r.BloomFilter != nil {
		return r.BloomFilter.Empty()
	}
	return len(r.DigestNodes) == 0
}

func (r *IntegrityInfo) GetDigestTree() *DigestTree {
	if r == nil {
		return nil
	}
	return r.DigestTree
}

func NewDigestIntegrityInfo(digestNodes []*DigestNode) *IntegrityInfo {
	return &IntegrityInfo{
		DigestNodes: digestNodes,
		DigestTree:  NewDigestTree(digestNodes, digester.NewMD5Digester()),
	}
}

func NewBloomFilterIntegrityInfo(digestNodes []*DigestNode) *IntegrityInfo {
	m, k := bloom_filter.EnsureFalsePositiveProbabilityUnder(bloom_filter.DefaultFalsePositive, int64(len(digestNodes)))
	bf := bloom_filter.NewBloomFilter(m, k)
	for _, digestNode := range digestNodes {
		bf.Add(digestNode.Digest)
	}
	return &IntegrityInfo{
		DigestNodes: digestNodes,
		BloomFilter: bf,
	}
}

func (r *IntegrityInfo) Count() int {
	return len(r.DigestNodes)
}

func (r *IntegrityInfo) Print() {
	if r.DigestTree != nil {
		fmt.Printf("Digest Tree: %s", r.DigestTree.ToJSON())
	}
}

func (r *IntegrityInfo) CompareCommon(another *IntegrityInfo) *CompareResult {
	result := &CompareResult{IsComparable: true}
	for i, digestNode := range r.DigestNodes {
		if !utils.EqualSlice(another.DigestNodes[i].Digest, digestNode.Digest) {
			result.UnequalChunks = append(result.UnequalChunks, UnequalChunk{
				ChunkNo: 0,
				Node:    digestNode,
			})
		}
	}
	return result
}

func (r *IntegrityInfo) CompareDigestTree(another *IntegrityInfo) *CompareResult {
	fmt.Println("Compare Digest Tree")
	srcCheckQueue := llq.New()
	tgtCheckQueue := llq.New()
	srcCheckQueue.Enqueue(r.DigestTree.Root)
	tgtCheckQueue.Enqueue(another.DigestTree.Root)
	result := &CompareResult{
		IsComparable: true,
		DigestTree:   another.DigestTree,
	}
	for !srcCheckQueue.Empty() {
		src, _ := srcCheckQueue.Dequeue()
		tgt, _ := tgtCheckQueue.Dequeue()
		srcNode := src.(*DigestNode)
		tgtNode := tgt.(*DigestNode)
		if !srcNode.Equal(tgtNode) {
			if srcNode.IsLeaf() {
				result.UnequalChunks = append(result.UnequalChunks, UnequalChunk{
					ChunkNo: srcNode.GetChunkNo(),
					Node:    srcNode,
				})
				continue
			}

			for _, childNode := range srcNode.Children {
				srcCheckQueue.Enqueue(childNode)
			}
			for _, childNode := range tgtNode.Children {
				tgtCheckQueue.Enqueue(childNode)
			}
		}
	}

	return result
}

func (r *IntegrityInfo) CompareBloomFilter(another *IntegrityInfo) *CompareResult {
	result := &CompareResult{IsComparable: true}
	for _, digestNode := range r.DigestNodes {
		if !another.BloomFilter.MightContain(digestNode.Digest) {
			result.UnequalChunks = append(result.UnequalChunks, UnequalChunk{
				ChunkNo: digestNode.GetChunkNo(),
				Node:    digestNode,
			})
		}
	}
	return result
}

func (r *IntegrityInfo) Compare(another *IntegrityInfo) *CompareResult {
	if r == nil || another == nil || r.Count() != another.Count() {
		return &CompareResult{IsComparable: false}
	}

	switch {
	case r.BloomFilter != nil && another.BloomFilter != nil:
		return r.CompareBloomFilter(another)
	case r.DigestTree != nil && another.DigestTree != nil:
		return r.CompareDigestTree(another)
	default:
		return r.CompareCommon(another)
	}
}
